﻿Imports Runa.Common.Helper
Imports System.Text
Imports Runa.Foundation.Runtime.Serialization
Imports System.IO.IsolatedStorage
Imports Runa.Foundation
Imports Runa.Foundation.Collections
Imports System.IO

#If Not WINDOWS_PHONE Then
Imports System.Collections.Concurrent
#End If

Namespace Diagnostics

    Public NotInheritable Class Tracer
        Implements IDisposable

        Public Property Log() As New List(Of System.MutableTuple(Of DateTime, String))
        Public Property LogFileName() As String
        Public Property AutoFlush() As Boolean = True

#If WINDOWS_PHONE Then
        Public Shared Property Logs() As New Dictionary(Of String, Tracer)
#Else
        Public Shared Property Logs() As New ConcurrentDictionary(Of String, Tracer)
#End If

        Public Shared ReadOnly Property [Default]() As Tracer
            Get
                Try
                    Return Tracer.Logs("Default")
                Catch ex As Exception
                    Return Nothing
                End Try
            End Get
        End Property

        Shared Sub New()
            Tracer.Add("Default", GetApplicationPackageID() & ".log.xml")
        End Sub

        Private Sub New(ByVal fn As String)
            If fn.EndsWith(".log.xml") = False Then fn += ".log.xml"
            Me.LogFileName = fn
            ' If there has been an exist log file, we should load it.
            If FileStorage.FileExists(Me.LogFileName) Then
                Try
                    Using fs = FileStorage.OpenFile(Me.LogFileName, Runa.Foundation.IO.FileMode.Open)
                        Me.Log = Serializer.XmlSerializer.Deserialize(Of List(Of System.MutableTuple(Of DateTime, String)))(fs)
                        fs.Dispose()
                    End Using
                Catch ex As Exception
                    FileStorage.DeleteFile(Me.LogFileName)
                End Try
            End If
        End Sub

        Public Shared Function Add(ByVal name As String, Optional ByVal fn As String = "") As Tracer
            If fn = "" Then fn = name
#If WINDOWS_PHONE Then
            Try
                Dim result = New Tracer(fn)
                Tracer.Logs.Add(name, result)
                Return result
            Catch ex As Exception
                Return Nothing
            End Try
#Else
            Return Tracer.Logs.GetOrAdd(name, Function() New Tracer(fn))
#End If
        End Function

        Public Shared Function Remove(ByVal name As String, Optional ByRef result As Tracer = Nothing) As Boolean
#If WINDOWS_PHONE Then
            Try
                If Tracer.Logs.ContainsKey(name) Then
                    result = Tracer.Logs(name)
                    Tracer.Logs.Remove(name)
                    Return True
                Else
                    result = Nothing
                    Return False
                End If
            Catch ex As Exception
                result = Nothing
                Return False
            End Try
#Else
            Return Tracer.Logs.TryRemove(name, result)
#End If
        End Function

        Public Shared Sub RemoveAsync(ByVal name As String, ByVal callback As Action(Of Tracer))
            Dim worker =
                Function()
                    Dim result As Tracer = Nothing
                    While True
                        If Remove(name, result) Then
                            If result IsNot Nothing Then
                                Exit While
                            End If
                        End If
                    End While
                    Return result
                End Function
            worker.BeginInvoke(
                Sub(ia)
                    Dim state = CType(ia.AsyncState, Func(Of Tracer))
                    Dim r = state.EndInvoke(ia)
                    callback.BeginInvoke(r,
                        Sub(_ia)
                            Dim _state = CType(_ia.AsyncState, Action(Of Tracer))
                            _state.EndInvoke(_ia)
                        End Sub, callback)
                End Sub, worker)
        End Sub

#If NETFX_CORE Then

        Public Sub TraceMessage(message As String,
                <CallerMemberName> Optional memberName As String = Nothing,
                <CallerFilePath> Optional sourcefilePath As String = Nothing,
                <CallerLineNumber()> Optional sourceLineNumber As Integer = 0)
            Dim sb As New StringBuilder()
            sb.AppendLine("Message: " & message)
            sb.AppendLine("Function: " & memberName)
            sb.AppendLine("Source: " & sourcefilePath)
            sb.AppendLine("Line: " & sourceLineNumber)
            Me.Log.Add(New System.MutableTuple(Of DateTime, String)(DateTime.Now, sb.ToString().Trim()))
            If Me.AutoFlush Then Me.Flush()
        End Sub

#Else

        Public Sub TraceMessage(message As String,
                Optional memberName As String = Nothing,
                Optional sourcefilePath As String = Nothing,
                Optional sourceLineNumber As Integer? = Nothing)
            Dim sb As New StringBuilder()
            Dim inf = GetCallerInformation(New StackFrame(), 0)
            With inf
                sb.AppendLine("Message: " & message)
                sb.AppendLine("Function: " & If(memberName Is Nothing, .Item1, memberName))
                sb.AppendLine("Source: " & If(sourcefilePath Is Nothing, .Item2, sourcefilePath))
                sb.AppendLine("Line: " & If(sourceLineNumber Is Nothing, .Item3, sourceLineNumber))
            End With
            Me.Log.Add(New System.MutableTuple(Of DateTime, String)(DateTime.Now, sb.ToString().Trim()))
            If Me.AutoFlush Then Me.Flush()
        End Sub

#End If

        Public Sub Flush()
            If FileStorage.DirectoryExists(Path.GetDirectoryName(Me.LogFileName)) = False Then
                FileStorage.CreateDirectory(Path.GetDirectoryName(Me.LogFileName))
            End If
            Using fs = FileStorage.OpenFile(Me.LogFileName, Runa.Foundation.IO.FileMode.OpenOrCreate)
                Using ms = Serializer.XmlSerializer.Serialize(Me.Log)
                    Dim data = ms.ToArray()
                    fs.Write(data, 0, data.Length)
                End Using
                fs.Flush()
            End Using
        End Sub

        Public Function GetData() As Byte()
            Using ms = Serializer.XmlSerializer.Serialize(Me.Log)
                Return ms.ToArray()
            End Using
        End Function

#Region "IDisposable Support"
        Private disposedValue As Boolean ' To detect redundant calls

        ' IDisposable
        Protected Sub Dispose(disposing As Boolean)
            If Not Me.disposedValue Then
                If disposing Then
                    ' TODO: dispose managed state (managed objects).
                    Me.Flush()
                End If
                ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
                ' TODO: set large fields to null.
#If Windows And (Not NETFX_CORE) Then
                Dim data = Me.GetData()
                Dim cmds = System.Environment.GetCommandLineArgs()
                Dim dir = System.IO.Path.GetDirectoryName(cmds(0))
                Dim p = Path.Combine(dir, DateTime.Now.ToBinary().ToString() & ".xml")
                File.WriteAllBytes(p, data)
#End If

                Me.Log = Nothing
            End If
            Me.disposedValue = True
        End Sub

        ' TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources.
        Protected Overrides Sub Finalize()
            ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
            Dispose(False)
            MyBase.Finalize()
        End Sub

        ' This code added by Visual Basic to correctly implement the disposable pattern.
        Public Sub Dispose() Implements IDisposable.Dispose
            ' Do not change this code.  Put cleanup code in Dispose(disposing As Boolean) above.
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub
#End Region

    End Class

End Namespace